package globox_db_server;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import utils.jn.Sit;
import utils.jn.State;
import utils.jn.TheaterSit;

public class SessionHandler implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 7550087272186408267L;

	private final String name;

	private TheaterSit[] sits;

	private HashMap<Integer, List<TheaterSit>> reserved;

	private Lock mutex;

	/**
	 * Constructor
	 * 
	 * @throws RemoteException
	 */
	public SessionHandler(String name, int noRow, int noColumn)
			throws RemoteException {
		super();
		this.name = name;
		this.sits = new TheaterSit[noRow * noColumn];
		for (int i = 0; i < noRow; i++)
			for (int j = 0; j < noColumn; j++)
				sits[(i * noColumn) + j] = new TheaterSit(i, j);
		this.reserved = new HashMap<Integer, List<TheaterSit>>();
		this.mutex = new ReentrantLock();
	}

	/**
	 * Make a reservation
	 * 
	 * @return
	 */
	public Iterable<Sit> makeReserve(int id) {
		ArrayList<Sit> map = new ArrayList<Sit>();
		boolean flag = true;
		try {
			this.mutex.lock();
			for (TheaterSit sit : this.sits)
				if (sit.state().equals(State.FREE) && flag) {
					map.add(sit.reserve());
					sit.occupy(id);
					this.reserved.put(id, new ArrayList<TheaterSit>());
					this.reserved.get(id).add(sit);
					flag = false;
				} else
					map.add(sit);
		} finally {
			this.mutex.unlock();
		}
		return map;
	}

	public Iterable<Sit> makeReserve(int id, Iterable<Sit> sits) {
		if (this.reserved.containsKey(id))
			this.free(id);
		else {
			try {
				this.mutex.lock();
				this.reserved.put(id, new ArrayList<TheaterSit>());
			} finally {
				this.mutex.unlock();
			}
		}
		Iterator<Sit> it = sits.iterator();
		Sit tmp = it.next();
		ArrayList<Sit> map = new ArrayList<Sit>();
		try {
			this.mutex.lock();
			for (TheaterSit sit : this.sits) {
				if (sit.equals(tmp)) {
					if (!sit.state().isOccupied()) {
						map.add(sit.reserve());
						sit.occupy(id);
						this.reserved.get(id).add(sit);
					}
					tmp = it.hasNext() ? it.next()
							: null;
				} else
					map.add(sit);
			}
		} finally {
			this.mutex.unlock();
		}
		return map;
	}

	/**
	 * Its frees the reserved sits
	 * 
	 * @param id
	 */
	public void free(int id) {
		try {
			this.mutex.lock();
			if (this.reserved.containsKey(id)) {
				for (TheaterSit sit : this.reserved.get(id))
					sit.free();
				this.reserved.get(id).clear();
			}
		} finally {
			this.mutex.unlock();
		}
	}

	/** return the seats of this theater */
	public Iterable<Sit> getSeats() {
		List<Sit> seats = new ArrayList<Sit>();
		for (TheaterSit ts : sits)
			seats.add(ts);
		return seats;
	}

	/**
	 * confirm the transaction
	 * 
	 * @param transactionID
	 * @return
	 */
	public boolean commit(int transactionID) {
		boolean finished = false;
		try {
			this.mutex.lock();
			finished = this.reserved.remove(transactionID) != null;
		} finally {
			this.mutex.unlock();
		}
		return finished;
	}

	public void save(String path) {
		try {
			File data = new File(path);
			ObjectOutputStream out = new ObjectOutputStream(
					new FileOutputStream(data));
			out.writeObject(this);
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static SessionHandler load(String path) {
		SessionHandler newobject = null;
		try {
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(
					path));
			newobject = (SessionHandler) in.readObject();
			in.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return newobject;
	}

	public String toString() {
		return this.name;
	}
}
